﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using LilTmSimFacade;
using StateMachine;
using Microsoft.Win32;
using System.IO;

namespace LilTmSim
{
	/// <summary>
	/// Interaction logic for TuringTab.xaml
	/// </summary>
	public partial class TuringTab : UserControl, IMachineTab
	{
		public const string DELTA = "Δ";

		/// <summary>
		/// Header is a Dependency Property.
		/// </summary>
		public string Header
		{
			get { return (string)GetValue(HeaderProperty); }
			set { SetValue(HeaderProperty, value); }
		}
		public static readonly DependencyProperty HeaderProperty =
			DependencyProperty.Register("Header", typeof(string), typeof(TuringTab), new UIPropertyMetadata("{untitled}", new PropertyChangedCallback(HeaderChanged)));

		static void HeaderChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{

		}

		/// <summary>
		/// InitialState is a Dependency Property.
		/// </summary>
		public StateNode InitialState
		{
			get { return (StateNode)GetValue(InitialStateProperty); }
			set { SetValue(InitialStateProperty, value); }
		}
		public static readonly DependencyProperty InitialStateProperty =
			DependencyProperty.Register("InitialState", typeof(StateNode), typeof(TuringTab), new UIPropertyMetadata(null));

		/// <summary>
		/// SelectedStates is a Dependency Property.
		/// </summary>
		public StateNodeCollection SelectedStates
		{
			get { return (StateNodeCollection)GetValue(SelectedStatesProperty); }
			set { SetValue(SelectedStatesProperty, value); }
		}
		public static readonly DependencyProperty SelectedStatesProperty =
			DependencyProperty.Register("SelectedStates", typeof(StateNodeCollection), typeof(TuringTab), new UIPropertyMetadata(null));

		/// <summary>
		/// DefaultTransitionState is a Dependency Property.
		/// </summary>
		public string DefaultTransitionState
		{
			get { return (string)GetValue(DefaultTransitionStateProperty); }
			set { SetValue(DefaultTransitionStateProperty, value); }
		}
		public static readonly DependencyProperty DefaultTransitionStateProperty =
			DependencyProperty.Register("DefaultTransitionState", typeof(string), typeof(TuringTab), new UIPropertyMetadata(""));

		/// <summary>
		/// Gets the collection of StateNodes in the turing machine tab
		/// </summary>
		public StateNodeCollection States
		{
			get
			{
				return Resources["states"] as StateNodeCollection;
			}
		}

		/// <summary>
		/// Gets the collection of StateNodes in the turing machine tab
		/// </summary>
		public TransitionNodeCollection Transitions
		{
			get
			{
				return Resources["transitions"] as TransitionNodeCollection;
			}
		}

		public NoteNodeCollection Notes
		{
			get
			{
				return Resources["notes"] as NoteNodeCollection;
			}
		}

		/// <summary>
		/// TMProvider is a Dependency Property.
		/// </summary>
		public StateMachineProvider TMProvider
		{
			get { return (StateMachineProvider)GetValue(TMProviderProperty); }
			set { SetValue(TMProviderProperty, value); }
		}
		public static readonly DependencyProperty TMProviderProperty =
			DependencyProperty.Register("TMProvider", typeof(StateMachineProvider), typeof(TuringTab), new UIPropertyMetadata(null));


		public bool HasBeenSaved { get; set; }
		public StateNode NodeMouseOver { get { return States.FirstOrDefault(node => node.IsMouseOver); } }

		public TuringTab() : this(new StateMachineProvider(StateMachines.Turing)) { }
		public TuringTab(string path)
			: this(new StateMachineProvider(path))
		{
			this.Header = path.Substring(path.Replace("\\", "/").LastIndexOf("/") + 1);
		}

		public TuringTab(StateMachineProvider p)
		{
			Initialized += delegate
			{
				SelectedStates = new StateNodeCollection();

				canvas.MouseDown += new MouseButtonEventHandler(canvas_MouseDown);
				canvas.OnStateNodesSelected += new GridCanvas.StateNodeCollectionResult(canvas_OnStateNodesSelected);

				States.OnItemInserted += new StateNodeCollection.CollectionAndNode(States_OnItemInserted);
				States.OnItemRemoved += new StateNodeCollection.CollectionAndNode(States_OnItemRemoved);
				Transitions.OnItemInserted += new TransitionNodeCollection.CollectionAndNode(Transitions_OnItemInserted);
				Transitions.OnItemRemoved += new TransitionNodeCollection.CollectionAndNode(Transitions_OnItemRemoved);

				this.KeyDown += new KeyEventHandler(TuringTab_KeyDown);

				TMProvider = p;
				LoadMachine(TMProvider.Machine);

				//this.MouseMove += new MouseEventHandler(TuringTab_MouseMove);

			};

			InitializeComponent();
		}

		void TuringTab_MouseMove(object sender, MouseEventArgs e)
		{
			//			Application.Current.MainWindow.Title = e.MouseDevice.GetPosition(this as IInputElement).ToString();
		}

		private void LoadMachine(TuringMachine tm)
		{
			Dictionary<State, StateNode> stateNodeDictionary = new Dictionary<State, StateNode>(tm.States.Count);
			foreach (State s in tm.States)
			{
				StateNode n = AddState(s);
				stateNodeDictionary.Add(s, n);
			}

			foreach (State s in tm.States)
			{
				StateNode start = stateNodeDictionary[s];

				foreach (Transition t in s.Transitions)
				{
					StateNode end = stateNodeDictionary[t.EndState];
					AddTransition(t, start, end);
				}

				start.StateData = s;
			}

			foreach (Note n in tm.Notes)
			{
			}
		}

		private TransitionNode AddTransition(Transition t, StateNode startState, StateNode endState)
		{
			TransitionNode n = new TransitionNode(t.MidPoint == new Point(0, 0));

			n.StartStateNode = startState;
			n.EndStateNode = endState;

			AddTransition(n);

			n.Read = t.Read != null ? t.Read.ToString() : DELTA;
			n.Write = t.Write != null ? t.Write.ToString() : DELTA;
			n.Move = t.Direction;

			n.TransitionData = t;

			if (!n.AutoCenterMiddle && n.StartStateNode != n.EndStateNode)
				n.MiddlePoint = t.MidPoint;

			n.IsPathVisible = t.IsPathVisible;

			n.InvalidateVisual();
			return n;
		}

		private StateNode AddState(State s)
		{
			StateNode n = new StateNode();

			n.IsFinal = s.IsFinal;
			n.IsInitial = s.IsInitial;
			n.Label = s.Label;
			n.Name = s.Name;
			n.Location = s.Location;
			n.ShowLabel = s.ShowLabel;

			this.AddState(n);

			return n;
		}

		void States_OnItemInserted(StateNodeCollection sender, StateNode node)
		{
			canvas.Children.Add(node);
		}

		void States_OnItemRemoved(StateNodeCollection sender, StateNode node)
		{
			TMProvider.Machine.States.Remove(node.StateData);
			while (node.TransitionsEntering.Count > 0)
			{
				RemoveTransition(node.TransitionsEntering[0]);
			}
			while (node.TransitionsLeaving.Count > 0)
			{
				RemoveTransition(node.TransitionsLeaving[0]);
			}
			canvas.Children.Remove(node);
		}

		void Transitions_OnItemInserted(TransitionNodeCollection sender, TransitionNode node)
		{
			canvas.Children.Add(node);
		}

		void Transitions_OnItemRemoved(TransitionNodeCollection sender, TransitionNode node)
		{
			node.StartStateNode.StateData.Transitions.Remove(node.TransitionData);
			canvas.Children.Remove(node);
		}

		void TuringTab_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.Key == Key.Delete && SelectedStates.Count > 0 && MessageBox.Show("Are you sure you want to delete the selected nodes?", "Confirm Delete", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
			{
				foreach (StateNode s in SelectedStates)
				{
					((StateNodeCollection)Resources["states"]).Remove(s);
				}
				SelectedStates.Clear();
			}
		}

		void canvas_OnStateNodesSelected(StateNodeCollection result)
		{
			DeselectStates();
			SelectedStates = result;
			SelectStates();
		}

		private void SelectStates()
		{
			foreach (StateNode sn in SelectedStates)
			{
				sn.IsSelected = true;
			}
		}

		private void DeselectStates()
		{
			foreach (StateNode sn in SelectedStates)
			{
				sn.IsSelected = false;
			}
		}



		Point mouseDownLocation;
		StateNode start;
		void canvas_MouseDown(object sender, MouseButtonEventArgs e)
		{
			mouseDownLocation = e.GetPosition(this as IInputElement);
			if (e.LeftButton == MouseButtonState.Pressed)
			{
				if (Keyboard.IsKeyDown(Key.LeftCtrl) && NodeMouseOver != null)
				{
					start = NodeMouseOver;
					this.MouseUp += new MouseButtonEventHandler(TuringTab_MouseUp_AddTransition);
				}
				else
				{
					if (e.ClickCount == 2)
					{
						if (Mouse.DirectlyOver == canvas as IInputElement)
						{
							StateNode s = AddState();
							TMProvider.Machine.States.Add(s.StateData);
						}
					}
					else if (SelectedStates.Count > 0)
					{
						StateNode over = NodeMouseOver;
						if (over != null && over.IsSelected)
						{
							canvas.MouseMove += new MouseEventHandler(canvas_MassDragMove);
							canvas.MouseUp += new MouseButtonEventHandler(canvas_MassDragEnd);

							canvas.MouseDown -= new MouseButtonEventHandler(canvas_MouseDown);
						}
						else
						{
							DeselectStates();
							SelectedStates.Clear();
						}
					}
				}
			}
		}

		void TuringTab_MouseUp_AddTransition(object sender, MouseButtonEventArgs e)
		{
			this.MouseUp -= new MouseButtonEventHandler(TuringTab_MouseUp_AddTransition);
			if (start != null && NodeMouseOver != null)
			{
				AddTransition(start, NodeMouseOver);
			}
			start = null;
		}

		public void AddTransition(StateNode st, StateNode end)
		{
			TransitionNode transitionNode = new TransitionNode();
			transitionNode.StartStateNode = st;
			transitionNode.EndStateNode = end;

			st.StateData.Transitions.Add(transitionNode.TransitionData);

			AddTransition(transitionNode);
		}

		private void AddTransition(TransitionNode transitionNode)
		{
			transitionNode.StartStateNode.TransitionsLeaving.Add(transitionNode);
			transitionNode.EndStateNode.TransitionsEntering.Add(transitionNode);

			Transitions.Add(transitionNode);
			Canvas.SetZIndex(transitionNode, 11);

			if (transitionNode.StartStateNode != transitionNode.EndStateNode)
			{
				transitionNode.SetBinding(TransitionNode.StartPointProperty, new Binding { Source = transitionNode.StartStateNode, Path = new PropertyPath("Location") });
				transitionNode.SetBinding(TransitionNode.EndPointProperty, new Binding { Source = transitionNode.EndStateNode, Path = new PropertyPath("Location") });
			}
			else
			{
				MultiBinding converter = new MultiBinding();
				converter.Converter = new StartStatePositionConverter();

				Binding b1 = new Binding { Source = transitionNode.StartStateNode, Path = new PropertyPath("Location") };
				Binding b2 = new Binding { Source = transitionNode, Path = new PropertyPath("MiddlePoint") };
				Binding b3 = new Binding { Source = transitionNode.EndStateNode, Path = new PropertyPath("Location") };

				converter.Bindings.Add(b1);
				converter.Bindings.Add(b3);
				converter.Bindings.Add(b2);

				transitionNode.SetBinding(TransitionNode.StartPointProperty, converter);
				transitionNode.SetBinding(TransitionNode.EndPointProperty, b3);

				//Position midpoint
				transitionNode.AutoCenterMiddle = false;
				transitionNode.CenterMiddlePoint();
				transitionNode.MiddlePoint = new Point(transitionNode.MiddlePoint.X + 60, transitionNode.MiddlePoint.Y - 60);

			}

			transitionNode.OnTransitionDeleteWanted += new TransitionNode.SenderOnly(transitionNode_OnTransitionDeleteWanted);
			transitionNode.InvalidateVisual();

		}

		void transitionNode_OnTransitionDeleteWanted(object sender)
		{
			RemoveTransition(sender as TransitionNode);
		}

		public void RemoveTransition(TransitionNode t)
		{
			Transitions.Remove(t);
			t.StartStateNode.TransitionsLeaving.Remove(t);
			t.EndStateNode.TransitionsEntering.Remove(t);
		}

		void canvas_MassDragEnd(object sender, MouseButtonEventArgs e)
		{
			canvas.MouseMove -= new MouseEventHandler(canvas_MassDragMove);
			canvas.MouseUp -= new MouseButtonEventHandler(canvas_MassDragEnd);

			canvas.MouseDown += new MouseButtonEventHandler(canvas_MouseDown);

			foreach (StateNode state in SelectedStates)
			{
				SnapNodeToGrid(state);
			}
		}

		void canvas_MassDragMove(object sender, MouseEventArgs e)
		{
			Point newP = e.GetPosition(this as IInputElement);

			double offsetx = mouseDownLocation.X - newP.X;
			double offsety = mouseDownLocation.Y - newP.Y;

			foreach (StateNode n in SelectedStates)
			{
				n.X -= offsetx;
				n.Y -= offsety;
			}

			mouseDownLocation = newP;
		}

		private void btnShowGrid_Click(object sender, RoutedEventArgs e)
		{
			canvas.IsGridVisible = btnShowGrid.IsChecked.Value;
		}

		private void AddState_MenuItem_Click(object sender, RoutedEventArgs e)
		{
			AddState();
		}

		private StateNode AddState()
		{
			StateNode n = new StateNode();
			Point point = Mouse.GetPosition(canvas as IInputElement);
			n.X = point.X;
			n.Y = point.Y;
			AddState(n);

			return n;
		}

		private void AddState(StateNode n)
		{
			n.Container = States;
			SnapNodeToGrid(n);
			States.Add(n);
			Canvas.SetZIndex(n, 21);

			n.OnIsInitialChanged += delegate(StateNode sender, bool value)
			{
				if (value)
				{
					if (InitialState != null) InitialState.IsInitial = false;
					InitialState = sender;
				}
				else
					InitialState = null;
			};

			n.OnDragEnd += delegate(StateNode sender) { SnapNodeToGrid(sender); };
		}

		private void SnapNodeToGrid(StateNode state)
		{
			state.X = state.X - (state.X % canvas.GridSize) + (canvas.GridSize / 2);
			state.Y = state.Y - (state.Y % canvas.GridSize) + (canvas.GridSize / 2);
		}

		private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
		{
			TextBox txt = (sender as TextBox);
			UpdateTM();
			Result r = TestString(txt.Text);
			if (r.Passed)
			{
				txt.Background = Brushes.LightGreen;
			}
			else
			{
				txt.Background = Brushes.Pink;
			}
			txtResult.Text = TMProvider.Machine.Tape.ToString();

			TraceStep.Visibility = Visibility.Collapsed;
			InitializeTrace.Content = "Trace";
			tapePrevious.Text = "";
			tapeHead.Text = "";

			if (r.ExceptionThrown)
			{
				txtResult.Text = "The machine is not a valid machine";
			}

		}

		private void UpdateTM()
		{
			foreach (StateNode n in States)
			{
				if (n.IsInitial)
					TMProvider.Machine.StartState = n.StateData;
				if (n.IsFinal)
					TMProvider.Machine.EndState = n.StateData;
			}
		}

		private Result TestString(string text)
		{
			Result r = null;

			try
			{
				bool accepted = TMProvider.Machine.Run(text);
				r = new Result(text, TMProvider.Machine.Tape.ToString(), accepted, false);
			}
			catch (Exception ex)
			{
				r = new Result(text, ex.Message, false, true);
			}

			return r;
		}


		private void Detrace()
		{
			States.ToList().ForEach(state => state.IsTracing = false);
		}

		private void TraceStep_Click(object sender, RoutedEventArgs e)
		{
			Detrace();

			bool valid = false;
			try
			{
				valid = TMProvider.Machine.Step();
			}
			catch { }


			if (TMProvider.Machine.CurrentState == TMProvider.Machine.EndState)
			{
				InputString.Background = Brushes.LightGreen;
			}
			else if (valid)
			{
				InputString.Background = Brushes.Yellow;
			}
			else
			{
				InputString.Background = Brushes.Pink;
			}

			States.First(x => x.StateData == TMProvider.Machine.CurrentState).IsTracing = true;


			resetTraceTapePreview();

		}

		private void resetTraceTapePreview()
		{
			string previous = string.Empty;
			for (int i = TMProvider.Machine.Tape.Head - 20; i < TMProvider.Machine.Tape.Head; i++)
			{
				if (TMProvider.Machine.Tape.TapeDict.ContainsKey(i))
					previous += (TMProvider.Machine.Tape.TapeDict[i] ?? 'Δ').ToString();
				else
					previous += DELTA;
			}
			tapePrevious.Text = previous;

			tapeHead.Text = (TMProvider.Machine.Tape.Current ?? 'Δ').ToString();


			string after = string.Empty;
			for (int i = TMProvider.Machine.Tape.Head + 1; i < TMProvider.Machine.Tape.Head + 20; i++)
			{
				if (TMProvider.Machine.Tape.TapeDict.ContainsKey(i))
					after += (TMProvider.Machine.Tape.TapeDict[i] ?? 'Δ').ToString();
				else
					after += DELTA;
			}

			txtResult.Text = after;
		}

		private void InitializeTrace_Click(object sender, RoutedEventArgs e)
		{
			if (TMProvider.Machine.ValidateMachine())
			{
				UpdateTM();
				Detrace();
				States.First(x => x.StateData == TMProvider.Machine.StartState).IsTracing = true;

				TraceStep.Visibility = Visibility.Visible;
				InputString.Background = Brushes.Yellow;
				TMProvider.Machine.Initialize(InputString.Text);
				InitializeTrace.Content = "Reset";

				resetTraceTapePreview();
			}
		}



		#region IMachineTab Members

		public void Save()
		{
			TMProvider.Save();
			HasBeenSaved = true;
		}

		public void Save(string path)
		{
			TMProvider.Save(path);
			Header = System.IO.Path.GetFileName(path);
			HasBeenSaved = true;
		}

		#endregion
		public ResultCollection Results { get { return Resources["results"] as ResultCollection; } }
		private void btnBatch_Click(object sender, RoutedEventArgs e)
		{
			OpenFileDialog open = new OpenFileDialog();

			if (open.ShowDialog(Application.Current.MainWindow).Value)
			{
				bool exception = false;
				int accepted = 0;
				int rejected = 0;

				string[] contents = File.ReadAllLines(open.FileName);
				toggle.IsChecked = true;

				Results.Clear();
				UpdateTM();
				foreach (string c in contents)
				{
					Result r = TestString(c);
					Results.Add(r);

					if (r.Passed)
						accepted++;
					else
						rejected++;

					if (r.ExceptionThrown)
						exception = true;
				}

				txtAccepted.Text = "Accepted: " + accepted.ToString();
				txtRejected.Text = "Rejected: " + rejected.ToString();

				if (exception)
					MessageBox.Show("The machine is invalid");
			}
		}

		private void lbTestResults_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			InputString.Text = (lbTestResults.SelectedItem as Result).StartTape;
		}

		private void txtMaxSteps_TextChanged(object sender, TextChangedEventArgs e)
		{
			if (TMProvider != null)
			{
				int max;
				bool worked = int.TryParse(txtMaxSteps.Text, out max);
				TMProvider.Machine.MaxSteps = worked ? max : 10000;
			}
		}


	}
}